/*
 * @Author: your name
 * @Date: 2021-08-16 09:08:02
 * @LastEditTime: 2021-08-16 09:14:26
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: /code/epoll.cpp
 */

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>

#define exit_if(r, ...) if(r) {printf(__VA_ARGS__); printf("error no: %d error msg %s\n", errno, strerror(errno)); exit(1);}

const int kReadEvent = EPOLLIN;
const int kWriteEvent = EPOLLOUT;

void SetNonBlock(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    exit_if(flags<0, "fcntl failed");
    int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    exit_if(r<0, "fcntl failed");
}
void SetReuseaddr(int fd)
{
    int reuse=1;
    setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof reuse);
}
void updateEvents(int epfd, int fd, int events, bool modify) {
    int ret;
    struct epoll_event event;
    event.data.fd=fd;
    event.events=events;
    if(!modify)
    {
        ret=epoll_ctl(epfd,EPOLL_CTL_ADD,event.data.fd,&event);
        exit_if(ret==-1,"epoll_ctl failed!");
    }
    else
    {
        ret=epoll_ctl(epfd,EPOLL_CTL_MOD,event.data.fd,&event);
        exit_if(ret==-1,"epoll_ctl failed!");
    }
    printf("%s fd %d events read %d write %d\n",
           modify ? "mod" : "add", fd, events & kReadEvent, events & kWriteEvent);
}
void HandleAccept(int efd, int fd) {
    struct sockaddr_in raddr;
    socklen_t rsz = sizeof(raddr);
    int cfd = accept(fd,(struct sockaddr *)&raddr,&rsz);
    exit_if(cfd<0, "accept failed");
    sockaddr_in peer, local;
    socklen_t alen = sizeof(peer);
    int r = getpeername(cfd, (sockaddr*)&peer, &alen);
    exit_if(r<0, "getpeername failed");
    printf("accept a connection from %s\n", inet_ntoa(raddr.sin_addr));
    SetNonBlock(cfd);
    updateEvents(efd, cfd, kReadEvent|kWriteEvent, false);
}

void HandleRead(int efd, int fd) {
    char buf[4096];
    int n = 0;
    while ((n=::read(fd, buf, sizeof buf)) > 0) {
        printf("read %d bytes by server\n", n);
        int r = ::write(fd, buf, n); //写出读取的数据
        //实际应用中，写出数据可能会返回EAGAIN，此时应当监听可写事件，当可写时再把数据写出
        exit_if(r<=0, "write error");
        printf("%d bytes sended by server\n",r);
    }
    if (n<0 && (errno == EAGAIN || errno == EWOULDBLOCK))
        return;
    exit_if(n<0, "read error"); //实际应用中，n<0应当检查各类错误，如EINTR
    printf("fd %d closed\n", fd);
    close(fd);
}

void HandleWrite(int efd, int fd) {
    //实际应用应当实现可写时写出数据，无数据可写才关闭可写事件
    updateEvents(efd, fd, kReadEvent, true);
}



void loop_once(int efd, int lfd, int waitms) {
    struct timespec timeout;
    timeout.tv_sec = waitms / 1000;
    timeout.tv_nsec = (waitms % 1000) * 1000 * 1000;
    const int kMaxEvents = 20;

    struct epoll_event events[kMaxEvents];
    int n=epoll_wait(efd,events,kMaxEvents,-1);
    printf("epoll_wait return %d\n", n);
    for(int i=0;i<n;i++)
    {
        int fd=events[i].data.fd;
        if(events[i].events==EPOLLIN)
            if(fd==lfd)
                HandleAccept(efd,fd);
            else
                HandleRead(efd,fd);
        else if(events[i].events==EPOLLOUT)
            HandleWrite(efd,fd);
        else 
            exit_if(1, "unknown event");
    }
}

int Init_Socket()
{
    short port = 9999;
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    exit_if(listenfd < 0, "socket failed");
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    int r = ::bind(listenfd,(struct sockaddr *)&addr, sizeof(struct sockaddr));
    exit_if(r, "bind to 0.0.0.0:%d failed %d %s", port, errno, strerror(errno));
    r = listen(listenfd, 20);
    exit_if(r, "listen failed %d %s", errno, strerror(errno));
    printf("fd %d listening at %d\n", listenfd, port);

    SetNonBlock(listenfd);
    SetReuseaddr(listenfd);
    return listenfd;
}

int main() {
    
    int epollfd = epoll_create(5);
    exit_if(epollfd < 0, "epoll_create failed");

    int lfd=Init_Socket();
    updateEvents(epollfd, lfd, kReadEvent, false);
    
    for (;;) { //实际应用应当注册信号处理函数，退出时清理资源
        loop_once(epollfd, lfd, 10000);
    }
    return 0;
}